<template>
  <div class="w-full uploader-wrapper" :class="isFlex ? 'flex' : ''">
    <uploader
      ref="uploaderRef"
      class="uploader"
      :key="componentsKey"
      :options="newOptions"
      :auto-start="false"
      :file-status-text="statusText"
      @upload-start="onUploadStart"
      @file-added="onFileAdded"
      @file-progress="onFileProgress"
      @file-success="onFileSuccess"
      @complete="complete"
      @file-error="onFileError"
      @file-removed="fileRemoved"
    >
      <uploader-drop v-if="isDrop" :style="{ height: height }">
        <uploader-btn :attrs="attrs">点击上传</uploader-btn>
        <!-- <uploader-btn :directory="true">&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;上传文件夹</uploader-btn> -->
        <span>&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;拖拽到此区域</span>
      </uploader-drop>
      <uploader-btn v-else :attrs="attrs">点击上传</uploader-btn>
      <uploader-list ref="uploaderListRef" v-if="isShowList"></uploader-list>
    </uploader>
    <span>
      （允许上传文件类型：{{ props.attrs.accept.join(",") }} 最大允许{{
        props.maxFileSize
      }}M）
    </span>
  </div>
</template>

<script setup>
import { ElMessage } from "element-plus";
import { formatToken, getToken } from "@/utils/auth";
import SparkMD5 from "spark-md5";
import {
  getSliceSize,
  getChunkUrl,
  completeMultipartUpload,
} from "@/api/uploader";
import { ACCEPT_CONFIG, getSuffix, wordToPdf } from "@/utils/uploader.js";
import uploader from "@/Uploader/components/uploader.vue";
import UploaderBtn from "@/Uploader/components/btn.vue";
import UploaderDrop from "@/Uploader/components/drop.vue";
// import UploaderUnsupport from '@/Uploader/components/unsupport.vue';
import UploaderList from "@/Uploader/components/list.vue";
// import UploaderFiles from '@/Uploader/components/files.vue';
// import UploaderFile from '@/Uploader/components/file.vue';
import { getCurrentInstance, computed } from "vue";
// getCurrentInstance.proxy 能获取到挂载在vue原型上的方法
const proxy = getCurrentInstance().proxy;
const emit = defineEmits([
  "add-file",
  "save-file",
  "save-file-list",
  "remove-file",
  "upload-start",
  "complete",
]);
const props = defineProps({
  // 是否可拖拽上传
  isDrop: {
    type: Boolean,
    default: true,
  },
  // 是否展示上传列表
  isShowList: {
    type: Boolean,
    default: true,
  },
  isFlex: {
    type: Boolean,
    default: true,
  },
  // 上传文件格式
  attrs: {
    type: Object,
    default: () => {
      // eg: {
      //   accept: ['.png', '.jpg', '.jpeg', '.gif']
      // };
      return {
        accept: [
          ...ACCEPT_CONFIG.image,
          ...ACCEPT_CONFIG.video,
          ...ACCEPT_CONFIG.audio,
          ...ACCEPT_CONFIG.document,
        ],
      };
    },
  },
  // 上传文件大小, 以M为单位
  maxFileSize: {
    type: Number,
    default: 100,
  },
  isSingle: {
    type: Boolean,
    default: false,
  },
  options: {
    type: Object,
    default: () => {},
  },
  // 可拖拽区域高度
  height: {
    type: String,
    default: "200px",
  },
});
const uploaderRef = ref(null);
const statusText = reactive({
  success: "成功了",
  error: "出错了",
  uploading: "上传中",
  paused: "暂停中",
  waiting: "等待中",
});
/* 生产环境url替换 */
const minioApi =
  import.meta.env.MODE !== "development"
    ? proxy.$config.ProxyApi?.minioApi
    : "/minioApi";
// 设置upload chunkSize
if (!localStorage.getItem("chunkSize")) {
  getSliceSize().then((res) => {
    const chunkSize = res.data || 5 * 1024 * 1024;
    localStorage.setItem("chunkSize", chunkSize);
  });
}
const defaultOptions = reactive({
  // 目标上传 URL，可以是字符串也可以是函数，如果是函数的话，则会传入 Uploader.File 实例、
  // 当前块 Uploader.Chunk 以及是否是测试模式，默认值为 '/'
  target: function (file, chunkFile) {
    const key = "chunk_" + chunkFile.offset; // 键值 用于获取分块链接URL
    let url = `${minioApi}/minio${file.chunkUrlData[key]}`;
    return url;
  },
  // 是否单选
  singleFile: true,
  // 这将允许在浏览器崩溃甚至计算机重新启动后继续上传。(默认: true)
  testChunks: false,
  // 分块大小
  chunkSize: localStorage.getItem("chunkSize") || 5 * 1024 * 1024,
  // 强制所有块小于或等于 chunkSize。否则，最后一个块将大于或等于chunkSize。(默认: false)
  forceChunkSize: true,
  // uploadMethod: 'PUT',
  // 包含在带有数据的多部分 POST 中的额外参数。这可以是一个对象或一个函数。如果是一个函数，
  // 它将被传递一个 Uploader.File、一个 Uploader.Chunk 对象和一个 isTest 布尔值（默认值{}：）
  query: (file, chunk) => {
    // const data = { partNumber: chunk.offset + 1 };
    // return data;
    const params = {
      uploadId: file.uploadId,
      sliceIndex: "chunk_" + chunk.offset,
    };
    return params;
  },
  uploadMethod: "POST",
  //  当上传的时候所使用的是方式，可选 multipart、octet，默认 multipart，参考 multipart vs octet。
  // MiniO 的分片不能使用表单
  method: "multipart",
  // Minio的连接后面不能拼接参数，所以设置为空
  processParams: function (params, file, chunk, isTest) {
    return params;
  },
  headers: {
    "Cup-Auth": formatToken(getToken()?.access_token),
  },
  allowDuplicateUploads: true,
});
const newOptions = reactive(Object.assign({}, defaultOptions, props.options));
// 添加文件
const onFileAdded = (file) => {
  emit("add-file", file);
  file.pause(); //先暂停，后续生成md5
  if (
    newOptions.max &&
    uploaderRef.value.uploader.fileList.length > newOptions.max
  ) {
    ElMessage.error("文件上传个数已达到最大限制！");
    // 取消上传且从文件列表中移除
    file.cancel();
    return;
  }
  const flag = file.size / 1024 / 1024 < props.maxFileSize;
  if (!flag) {
    ElMessage.error(file.name + "文件大小超出限制，请修改后重新上传！");
    file.cancel();
    return;
  }
  if (props.isSingle) {
    if (uploaderRef.value.uploader.fileList.length > 1) {
      ElMessage.error("不能同时上传多个文件！");
      uploaderRef.value.uploader.fileList.splice(0, 0);
      file.cancel();
      return;
    }
  }
  if (props.attrs.accept.length) {
    console.log(props.attrs.accept, `.${getSuffix(file.name)}`);
    if (
      !props.attrs.accept.includes(getSuffix(file.name)) &&
      !props.attrs.accept.includes(`.${getSuffix(file.name)}`)
    ) {
      ElMessage.error(file.name + "文件格式不正确，请修改后重新上传！");
      file.cancel();
      return;
    }
  }
  computeMD5(file, newOptions.chunkSize);
};
const onUploadStart = () => {
  emit("upload-start");
};
const onFileProgress = () => {};
const onFileError = () => {};
const computeMD5 = (file, chunkSize) => {
  console.log("开始计算MD5", file);
  const fileReader = new FileReader();
  const time = new Date().getTime();
  const blobSlice =
    File.prototype.slice ||
    File.prototype.mozSlice ||
    File.prototype.webkitSlice;
  let currentChunk = 0;
  const chunks = Math.ceil(file.size / chunkSize);
  const spark = new SparkMD5.ArrayBuffer();
  loadNext();
  fileReader.onload = (e) => {
    spark.append(e.target.result);
    if (currentChunk < chunks) {
      currentChunk++;
      loadNext();
    } else {
      const md5 = spark.end();
      // 获取文件后缀
      const h = getSuffix(file.name);
      file.uniqueIdentifier = md5 + "-" + new Date().getTime() + "." + h;
      console.log(
        `MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${
          file.size
        } 用时：${new Date().getTime() - time} ms`
      );
      if (md5 != "") {
        getUrls(file);
      }
    }
  };
  fileReader.onerror = function () {
    this.error(`文件${file.name}读取出错，请检查该文件`);
    file.cancel();
  };
  function loadNext() {
    const start = currentChunk * chunkSize;
    const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
    fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
  }
};
// 请求后端接口，获取分块url
const getUrls = (file) => {
  const fileName = file.uniqueIdentifier; // 文件名
  const fileSize = file.size; // 文件大小
  getChunkUrl({ fileName, fileSize }).then((res) => {
    file.chunkUrlData = res.data.urls;
    file.uploadId = res.data.uploadId;
    file.resume(); // 开始上传
  });
};
// 单个文件上传成功
const onFileSuccess = (rootFile, file, response) => {
  const res = JSON.parse(response);
  // // 服务器自定义的错误（即虽返回200，但是是错误的情况），这种错误是Uploader无法拦截的
  if (res.code != 200) {
    ElMessage.error(res.msg);
    // 文件状态设为“失败”
    return;
  }
  // 合并文件
  const uploadId = file.uploadId; // 分片数
  // 锦州项目要求，判断word或pdf，添加参数 wordToPdf=true
  completeMultipartUpload({ uploadId, wordToPdf: wordToPdf(file.name) }).then(
    function (response) {
      if (wordToPdf(file.name)) {
        file.wordToPdf = true; // 是否word转pdf
        file.previewUrl = response?.data?.previewPath; // word预览地址
      }
      file.downloadUrl = response?.data?.filePath; // 下载地址
      emit("save-file", file);
    }
  );
};
// 全部文件上传后
const complete = function () {
  emit("save-file-list", uploaderRef.value.uploader.fileList);
  emit("complete");
};
// 移除文件
const fileRemoved = (file) => {
  console.log("删除文件:::", file);
  emit("remove-file", file);
};
// 抛出清空文件列表方法
const componentsKey = ref("111");
const clearFileList = () => {
  componentsKey.value = Date.now();
};
let uploaderListRef = ref();
let initFileList = (data) => {
  uploaderListRef.value.updateList(data);
};
defineExpose({
  clearFileList,
  initFileList,
  uploader: uploaderRef,
  fileRemoved,
});
</script>

<style lang="scss" scoped>
@import url("@/Uploader/common/style.css");

.uploader {
  .uploader-drop {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    background: rgb(35 160 229 / 10%);
    border: 1px dashed #23a0e5;
  }

  .uploader-btn {
    font-size: 14px;
    color: #23a0e5;
    background: transparent;
    border: 0;
  }

  :deep .uploader-list ul {
    margin-top: 15px !important;
  }

  .uploader-file-progress {
    background: transparent !important;
  }

  .uploader-file[status="error"] .uploader-file-progress {
    background: #e34d59 !important;
  }
}
</style>
